﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Upreader.Application;
using System.Collections.Concurrent;
using Upreader.Application.Model;
using System.Threading;
using System.Threading.Tasks;
using Upreader.Common;

namespace Upreader.Engine
{
	/// <summary>
	/// Defines the engine that is responsible for downloading, repairing and extracting
	/// </summary>
	public class DownloadEngine
	{
		#region Fields

        private readonly DownloadEngineContext _context;

        private readonly DownloadSegmentContextLoader _downloadSegmentContextLoader;
        private readonly DownloadedSegmentProcessorHandler _downloadSegmentProcessorHandler;
        private readonly FileAssembleProcessor _fileAssembleProcessor;
        private readonly ServerLoader _serverLoader;

		#endregion // Fields

		#region Constructor

		public DownloadEngine(IApplicationService service)
			: this(service, new DownloadEngineConfiguration())
		{
		}

		public DownloadEngine(IApplicationService service, DownloadEngineConfiguration configuration)
		{
			if (service == null)
				throw new ArgumentNullException("service");
			if (configuration == null)
				throw new ArgumentNullException("configuration");

            _context = new DownloadEngineContext(service, configuration);

            // setup components
            _downloadSegmentContextLoader = new DownloadSegmentContextLoader(_context);
            _downloadSegmentProcessorHandler = new DownloadedSegmentProcessorHandler(_context); 
            _fileAssembleProcessor = new FileAssembleProcessor(_context);
            _serverLoader = new ServerLoader(_context);

            // assign events
            _serverLoader.ServerProcessorAdded += (sender, args) => OnServerProcessorAdded(args);
            _serverLoader.ServerProcessorRemoved += (sender, args) => OnServerProcessorRemoved(args);
		}

		#endregion // Constructor

		#region Properties

        /// <summary>
        /// Get the context that is used by this engine
        /// </summary>
        public DownloadEngineContext Context
        {
            get
            {
                return _context;
            }
        }

		#endregion // Properties

		#region Methods

        public Task StartDownloadTask(CancellationToken cancellationToken)
        {
            CancellationTokenSource innerCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
            CancellationToken innerCancellationToken = innerCancellationTokenSource.Token;

            Task[] tasks = new[]
            {
                _downloadSegmentContextLoader.Run(innerCancellationToken),
                _downloadSegmentProcessorHandler.Run(innerCancellationToken),
                _serverLoader.Run(innerCancellationToken),
                _fileAssembleProcessor.Run(innerCancellationToken)
            };

            return StartIsolatedTaskGroup(tasks, innerCancellationTokenSource);
        }

		#endregion // Methods

        #region Protected Methods

        protected Task StartIsolatedTaskGroup(Task[] tasks, CancellationTokenSource cancellationTokenSource)
        {
            if (tasks == null)
                throw new ArgumentNullException("tasks");

            CancellationToken cancellationToken = cancellationTokenSource.Token;

            foreach (Task task in tasks)
            {
                task.ContinueWith((taskResult) =>
                {
                    if (taskResult.IsFaulted)
                    {
                        // if one of the group crashes, cancel all
                        cancellationTokenSource.Cancel();
                    }
                });
            }

            Task result = Task.Factory.ContinueWhenAll(tasks, _ =>
            {
                try
                {
                    // Wrap all exceptions in a AggregateException
                    Task.WaitAll(tasks);
                }
                catch (AggregateException ex)
                {
                    AggregateException flattenedExceptions = ex.Flatten();

                    // throw any not TaskCanceledException
                    flattenedExceptions.Handle(x => x is TaskCanceledException);
                }
            }, cancellationToken);

            return result;
        }

        #endregion // Protected Methods

        #region Helpers

        private Task StartFileAssembleProcessorTask(CancellationToken cancellationToken)
        {
            return _fileAssembleProcessor.Run(cancellationToken);
        }

        private Task StartServerLoaderTask(CancellationToken cancellationToken)
        {
            return _serverLoader.Run(cancellationToken);
        }

		#endregion // Helpers

		#region EventHandlers

        protected void OnServerProcessorRemoved(ServerProcessorEventArgs args)
        {
            EventHandler<ServerProcessorEventArgs> eventHandler = ServerProcessorRemoved;
            if (eventHandler != null)
                eventHandler(this, args);
        }

        protected void OnServerProcessorAdded(ServerProcessorEventArgs args)
        {
            EventHandler<ServerProcessorEventArgs> eventHandler = ServerProcessorAdded;
            if (eventHandler != null)
                eventHandler(this, args);
        }

		#endregion // EventHandlers

		#region Events

        /// <summary>
        /// Event raised when a server got added
        /// </summary>
        public event EventHandler<ServerProcessorEventArgs> ServerProcessorAdded;

        /// <summary>
        /// Event raised when a server downloadSegmentContext got removed
        /// </summary>
        public event EventHandler<ServerProcessorEventArgs> ServerProcessorRemoved;

		#endregion // Events
	}
}
